package com.lc.wxauth.controller;

import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.google.gson.GsonBuilder;
import com.lc.wxauth.config.properties.WxPayProperties;
import com.lc.wxauth.utils.WxNotifyUtils;
import com.study.common.enums.WxPayMsgEnum;
import com.study.common.res.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: some desc
 * @author: LChuang
 * @date: 2023/9/14 16:23
 */
@Slf4j
@RestController
@RequestMapping("/wx/pay")
public class PayController {

    @Resource
    private WxPayService wxPayService;

    @Resource
    private WxPayProperties wxPayProperties;

    /**
     * 商户订单号
     */
    private final String orderNo = "ORDER_20230914165512423";
    /**
     * 商户退款单号
     */
    private final String refundNo = "REFUND_20230914165512423";

    /**
     * 商品价格，单位：分
     */
    private final Integer orderAmount = 4200;

    /**
     * 退款价格，单位：分
     */
    private final Integer refundAmount = 4000;


    /**
     * Native下单（即创建支付二维码）
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
     * 当订单创建完成后，后端调用<Native>下单接口获取支付二维码地址
     * 前端将二维码地址转换为图片进行显示
     */
    @GetMapping("/createOrder")
    public ApiResult createOrder() {
        try {
            WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
            // 订单描述
            request.setDescription("这是一个测试订单");
            // 商户订单号
            request.setOutTradeNo(orderNo);
            // 订单金额
            WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
            // 总金额（单位为分，只能为整数，即元*100）
            amount.setTotal(orderAmount);
            request.setAmount(amount);
            // 调用接口，并接收返回的支付二维码地址
            String codeUrl = wxPayService.createOrderV3(TradeTypeEnum.NATIVE, request);
            return ApiResult.success(codeUrl);
        } catch (WxPayException e) {
            log.error("创建支付订单失败：{}", e.getMessage());
            return ApiResult.fail(e.getMessage());
        }
    }


    /**
     * 查询订单（即查询支付状态）
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml
     * 当前端展示之后二维码之后，定时任务向后端查询订单支付状态，后端向微信端查询订单状态，如果支付完成，则更新数据库并返回前端支付成功
     */
    @GetMapping("getOrderStatus")
    public ApiResult getOrderStatus() throws WxPayException {
        // 传入 微信支付系统生成的订单号 或 商户订单号 ，此处使用商户订单号查询，mchid会在执行时中自动添加，无需手动设置，即下方的null
        WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, orderNo);
        // TODO 根据查询结果处理订单(数据库操作)
        log.info("查询订单：{}，订单状态：{}", result.getOutTradeNo(), result.getTradeState());
        return ApiResult.success(result);
    }


    /**
     * 关闭订单（即关闭支付二维码）
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml
     * 当订单超时未支付时，关闭系统订单同时关闭支付二维码，使其无效
     */
    @GetMapping("closeOrder")
    public ApiResult closeOrder() throws WxPayException {
        // 方式1：创建请求对象，传入商户订单号号，执行关闭，无返回结果
        // WxPayOrderCloseV3Request request = new WxPayOrderCloseV3Request();
        // request.setOutTradeNo(orderNo);
        // wxPayService.closeOrderV3(request);

        // 方式2：也可以直接传入商户订单号关闭
        wxPayService.closeOrderV3(orderNo);

        // TODO 数据库更新操作...
        return ApiResult.success();
    }

    private final ReentrantLock orderLock = new ReentrantLock();
    private final ReentrantLock refundLock = new ReentrantLock();


    /**
     * 1. 支付通知（必须）（即用户支付完成后，微信端的回调接口）
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
     * 当收到支付时，修改本地订单为支付成功状态，并返回给微信处理成功的消息
     *
     * 2. 退款通知（可选）（即调用申请退款接口后的回调通知）
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_11.shtml
     * 当收到退款通知时，根据退款完成情况修改系统订单状态，比如退款中改为已退款
     */
    @PostMapping("/notify")
    public Map<String, Object> nativeNotify(HttpServletRequest request, HttpServletResponse response, @RequestBody String notifyData) {
        // 获取请求头信息
        SignatureHeader signatureHeader = WxNotifyUtils.getSignatureHeader(request);
        // 将请求体json字符串转换为实体
        OriginNotifyResponse notifyResponse = new GsonBuilder().create().fromJson(notifyData, OriginNotifyResponse.class);
        // 支付成功通知
        if (WxPayMsgEnum.SUCCESS.getValue().equals(notifyResponse.getEventType())) {
            if (orderLock.tryLock()) {
                try {
                    WxPayOrderNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(notifyData, signatureHeader);
                    // TODO 根据此处结果处理订单支付信息，<insert/update>
                    log.info("【支付通知处理】成功，订单号：{}，订单状态：{}", result.getResult().getOutTradeNo(), result.getResult().getTradeState());
                    // 不报错即为顺利支付成功，response状态值200
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", "SUCCESS");
                    map.put("message", "支付成功通知，处理成功");
                    return map;
                } catch (WxPayException e) {
                    log.error("【支付通知处理】异常：{}", e.getMessage());
                    // 支付成功通知处理失败时需要将状态码修改为5xx/4xx，微信才会重新发送回调
                    response.setStatus(500);
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", "ERROR");
                    map.put("message", "系统异常");
                    return map;
                } finally {
                    orderLock.unlock();
                }
            } else {
                // 锁获取失败，返回异常，等待下次消息
                // 支付成功通知处理失败时需要将状态码修改为5xx/4xx，微信才会重新发送回调
                response.setStatus(503);
                Map<String, Object> map = new HashMap<>();
                map.put("code", "FAIL");
                map.put("message", "系统繁忙");
                return map;
            }
        } else { // 退款通知
            if (refundLock.tryLock()) { // 获取锁
                try {
                    // 解析退款结果通知
                    WxPayRefundNotifyV3Result result = wxPayService.parseRefundNotifyV3Result(notifyData, signatureHeader);
                    // TODO 根据此处结果处理订单退款信息，<insert/update>
                    log.info("【退款结果通知】正常，订单号：{}，订单状态：{}", result.getResult().getOutTradeNo(), result.getResult().getRefundStatus());
                    // 返回成功标识
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", "SUCCESS");
                    map.put("message", "退款结果通知：正常");
                    return map;
                } catch (WxPayException e) {
                    log.error("【退款结果通知】异常：{}", e.getMessage());
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", "ERROR");
                    map.put("message", "退款结果通知：异常");
                    return map;
                } finally {
                    refundLock.unlock();
                }
            } else {
                // 锁获取失败，返回异常，等待下次消息
                Map<String, Object> map = new HashMap<>();
                map.put("code", "FAIL");
                map.put("message", "系统繁忙");
                return map;
            }
        }
    }


    /**
     * 申请退款
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_9.shtml
     * 用户发起退款申请，后端向微信端发起退款申请
     */
    @GetMapping("/orderRefund")
    public ApiResult orderRefund() throws WxPayException {
        // 构建请求对象
        WxPayRefundV3Request request = new WxPayRefundV3Request();
        // 微信支付系统生成的订单号 和 商户订单号 二选一，此处使用商户订单号查询
        request.setOutTradeNo(orderNo);
        // 商户退款单号
        request.setOutRefundNo(refundNo);
        // 退款结果回调url，需要从配置类中手动传入，否则微信端不会发送回调信息
        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
        // 金额信息
        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        // 退款金额 单位为分，不大于订单总金额
        amount.setRefund(refundAmount);
        // 原订单金额 单位为分
        amount.setTotal(orderAmount);
        // 退款币种（必填，只支持CNY）
        amount.setCurrency("CNY");
        request.setAmount(amount);
        // 发起退款申请
        WxPayRefundV3Result result = wxPayService.refundV3(request);
        // TODO 处理返回接口，例如存储微信支付退款单号
        log.info("退款已发起，退款单号：{}", result.getRefundId());
        return ApiResult.success(result);
    }

    /**
     * 查询退款状态
     */
    @GetMapping("/getRefundStatus")
    public ApiResult getRefundStatus() throws WxPayException {
        // 传入商户退款单号查询
        WxPayRefundQueryV3Result result = wxPayService.refundQueryV3(refundNo);
        return ApiResult.success(result);
    }
}
